package cn.infinite.security.core.impl;

import cn.easier.club.base.utils.EncryptUtil;
import cn.infinite.security.annotation.AutoSignCheck;
import cn.infinite.security.core.SecretKeyManager;
import cn.infinite.security.core.SignatureValidator;
import cn.infinite.security.error.KeyNotFoundException;
import cn.infinite.security.error.SignatureValidateException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.TreeMap;

/**
 * 默认签名验证器
 */
@Slf4j
public class DefaultSignatureValidator implements SignatureValidator<HttpServletRequest, AutoSignCheck> {

    /**
     * 密钥管理器
     */
    private SecretKeyManager<SecretKey> secretKeyManager;

    public DefaultSignatureValidator(SecretKeyManager secretKeyManager) {
        this.secretKeyManager = secretKeyManager;
    }

    @Override
    public Boolean validate(HttpServletRequest request, AutoSignCheck autoSignCheck) throws SignatureValidateException {
        String signatureHeaderName = autoSignCheck.signatureHeaderName();
        String signature = request.getHeader(signatureHeaderName);
        if (StringUtils.isBlank(signature)) {
            throw new SignatureValidateException("请求头中缺少签名参数");
        }
        String[] includeParamNames = autoSignCheck.includeParamNames();
        String[] excludeParamNames = autoSignCheck.excludeParamNames();

        String appIdHeaderName = autoSignCheck.appIdHeaderName();
        String uniqueIdHeaderName = autoSignCheck.uniqueIdHeaderName();
        String businessHeaderName = autoSignCheck.businessHeaderName();

        String appId = request.getHeader(appIdHeaderName);
        String uniqueId = request.getHeader(uniqueIdHeaderName);
        String businessName = request.getHeader(businessHeaderName);

        if (StringUtils.isEmpty(appId) || StringUtils.isEmpty(uniqueId) || StringUtils.isEmpty(businessName)) {
            log.error("确实必要参数");
            throw new SignatureValidateException("确保 AppID、uniqueId、business参数正确");
        }

        //获取密钥
        SecretKey secretKey = null;
        try {
            secretKey = this.secretKeyManager.getSecretByAppId(appId);
            if (!secretKey.getBusiness().equalsIgnoreCase(businessName)) {
                log.error("业务不匹配", businessName);
                throw new SignatureValidateException("业务未开通");
            }
        } catch (KeyNotFoundException e) {
            log.error("未找到应用密钥信息=>{}", appId);
            throw new SignatureValidateException("appId参数错误");
        }
        String appSecret = secretKey.getAppSecret();

        TreeMap<String, String> needSignParams = null;

        if (ArrayUtils.isEmpty(includeParamNames) && ArrayUtils.isEmpty(excludeParamNames)) {
            //如果 没有设置 需要参与签名的参数 也没有设置无需签名的参数，则所有参数参与签名
            needSignParams = this.analysisExcludeParam(request.getParameterMap(),
                    excludeParamNames);
        } else if (ArrayUtils.isNotEmpty(includeParamNames)) {
            //如果设置了 需要签名的 参数列表
            needSignParams = this.analysisIncludeParam(request.getParameterMap(),
                    includeParamNames);
        } else if (ArrayUtils.isNotEmpty(excludeParamNames)) {
            //如果设置了 排除参与签名 参数列表
            needSignParams = this.analysisExcludeParam(request.getParameterMap(),
                    excludeParamNames);
        }

        String _signature = this.getSignature(needSignParams, uniqueId, appSecret);

        if (!signature.equals(_signature)) {
            log.error("签名验证失败=>{}", needSignParams);
            return false;
        }
        log.info("签名验证通过 business=>{} appID=>{}", businessName, appId);
        return true;
    }


    private TreeMap<String, String> analysisIncludeParam(Map<String, String[]> paramsMap, String[] paramsName) {
        TreeMap<String, String> params = new TreeMap<>();
        for (String key : paramsName) {
            String[] values = paramsMap.get(key);
            if (ArrayUtils.isEmpty(values)) {
                continue;
            }
            params.put(key, values[0]);
        }
        return params;
    }

    private TreeMap<String, String> analysisExcludeParam(Map<String, String[]> paramsMap, String[] paramsName) {
        TreeMap<String, String> params = new TreeMap<>();
        for (String key : paramsName) {
            paramsMap.remove(key);
        }
        paramsMap.forEach((key, values) ->
                params.put(key, values[0])
        );
        return params;
    }


    /**
     * 计算签名
     * <p>
     * 1.确保传入的参数Map是有序的
     * 2.参数使用 key1=value1&key2=value2 格式连接
     * 3.在末尾拼接唯一ID 和 密钥
     * 4.使用SHA进行摘要 然后小写
     *
     * @param params
     * @param uniqueId
     * @param secret
     * @return
     */
    private String getSignature(Map<String, String> params, String uniqueId, String secret) {
        StringBuilder stringBuilder = new StringBuilder();
        params.forEach((key, value) -> {
            stringBuilder.append("&").append(key).append("=").append(value);
        });
        String substring = stringBuilder.append(uniqueId).append(secret).substring(1);
        //转小写
        String sign = EncryptUtil.getHexSha1(substring).toLowerCase();
        return sign;
    }
}
